import base64
import pickle
import re
from random import randint

from django.contrib.auth import login, authenticate, logout
from django.db import DatabaseError
from django.urls import reverse
from django_redis import get_redis_connection
from django.conf import settings
from django.contrib.auth import mixins
from django.shortcuts import render, redirect
from django.views.generic.base import View
from django import http
import logging
import json

from carts.utils import merge_cart_cookie_to_redis
from goods.models import SKU
from meiduo_mall.utils.response_code import RETCODE
from .utils import generate_verify_email_url, check_verify_email_token, get_user_by_account
from .models import User, Address
from meiduo_mall.utils.views import LoginRequired
from celery_tasks.email.tasks import send_verify_email
from celery_tasks.sms.tasks import send_sms_code

logger = logging.getLogger('django')


class RegisterView(View):

    def get(self, request):
        return render(request, 'register.html')  # 到注册页面的视图

    def post(self, request):
        # 注册界面会过来表单，用post方法接收数据
        username = request.POST.get('username')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        mobile = request.POST.get('mobile')
        image_code = request.POST.get('image_code')
        sms_code_cilent = request.POST.get('sms_code')
        allow = request.POST.get('allow')  # 只有‘on’或者传过来是none所以判断时要注意

        # 校验数据
        if all(request.POST.dict().values()) is False:
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match('^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden('请输入5-20个字符的用户名')
        if not re.match('^[0-9A-Za-z]{8,20}', password):
            return http.HttpResponseForbidden('请输入8-20位的密码')
        if password != password2:
            return http.HttpResponseForbidden('两次输入的密码不一致')
        if not re.match('^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('您输入的手机号格式不正确')
        # if allow != 'on':   这里在之前的if all里面就判断了，所以不用写
        #     return http.HttpResponseForbidden('请勾选用户协议')
        # 短信验证码之后再来写
        # 创建连接对象
        redis_conn = get_redis_connection('verify_code')
        # 读取redis中的短信验证码的标识
        sms_code_server = redis_conn.get('sms_code_%s' % mobile)
        # 让短信验证码只能用一次
        redis_conn.delete('sms_code_%s' % mobile)
        # 进行校验
        if sms_code_server is None:
            return http.HttpResponseForbidden('验证码过期')
            # return render(request, 'register.html', {'sms_code_errmsg': '无效的验证码'})
        if sms_code_cilent != sms_code_server.decode():
            return http.HttpResponseForbidden('验证码输入有误')
            # return render(request, 'register.html', {'sms_code_errmsg': '验证码输入有误'})
        # try:
        # 保存注册的数据
        user = User.objects.create_user(username=username, password=password, mobile=mobile)
        # except DatabaseError: # 重定向到首页，加上了异常报错
        #     return render(request, 'register.html', {'register_errmsg': '注册失败'})
        login(request, user)  # 状态保持就是login这个方法
        # return redirect(reverse('contents:index'))
        response = redirect('/')
        response.set_cookie('username', user.username, max_age=settings.SESSION_COOKIE_AGE)
        return response


class UsernameCountView(View):  # 用户名是否重复
    def get(self, request, username):
        count = User.objects.filter(username=username).count()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})


class MobileCountView(View):  # 手机号码是否重复
    def get(self, request, mobile):
        count = User.objects.filter(mobile=mobile).count()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})


class LoginView(View):
    """用户登录"""

    def get(self, request):
        return render(request, 'login.html')

    def post(self, request):
        # 接受参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        remembered = request.POST.get('remembered')

        # 校验
        user = authenticate(request, username=username, password=password)
        if user is None:
            return render(request, 'login.html', {'account_errmsg': '用户名或密码错误'})
        # 状态保持
        login(request, user)

        if remembered != 'on':
            request.session.set_expiry(0)
        # 响应体创建
        response = redirect(request.GET.get('next') or '/')  # 构建响应对象
        # 登陆的时候设置cookie
        response.set_cookie('username', user.username, max_age=settings.SESSION_COOKIE_AGE if remembered else None)
        #  响应创建结束返回响应体
        merge_cart_cookie_to_redis(request, response)
        return response


class LogoutView(View):
    # 退出登陆
    def get(self, request):
        # 登陆状态清除
        logout(request)
        # 构建响应体
        response = redirect('/login/')
        response.delete_cookie('username')
        # 返回响应
        return response


class InfoView(mixins.LoginRequiredMixin, View):
    def get(self, request):
        return render(request, 'user_center_info.html')


class EmaileView(LoginRequired):
    # 添加邮箱
    def put(self, request):
        # 接受数据
        json_dict = json.loads(request.body.decode())
        email = json_dict.get('email')
        # 校验
        if email is None:
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
            return http.HttpResponseForbidden('邮箱格式错误')
        # 处理逻辑
        ## 获取登陆用户
        user = request.user
        ## 设置邮箱
        User.objects.filter(id=user.id, email='').update(email=email)

        ## 发送邮件使用celery
        verify_url = generate_verify_email_url(user)
        send_verify_email.delay(email, verify_url)

        # 响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})


class VerifyEmailView(View):
    def get(self, request):
        # 接受数据
        token = request.GET.get('token')

        # 校验参数
        if token is None:
            return http.HttpResponseForbidden('token过期')
        user = check_verify_email_token(token)
        if not user:
            return http.HttpResponseForbidden('无效的token')

        # 处理业务逻辑
        # 修改emile_active值为Ture
        try:
            user.email_active = True
            user.save()
        except Exception as e:
            logger.error(e)
            return http.HttpResponseServerError('激活邮箱失败')
        # 响应
        return redirect('/info/')


class AddressView(LoginRequired):
    """用户收货地址"""

    def get(self, request):
        """提供收货地址界面"""
        user = request.user  # 获取用户
        address_qs = Address.objects.filter(user=user, is_deleted=False)  # 获取查询集
        address_list = []  # 搞一个空列表
        # 遍历查询集,整成字典
        for address in address_qs:
            address_dict = {
                'id': address.id,
                'title': address.title,
                'receiver': address.receiver,
                'province_id': address.province_id,
                'province': address.province.name,
                'city_id': address.city_id,
                'city': address.city.name,
                'district_id': address.district_id,
                'district': address.district.name,
                'place': address.place,
                'mobile': address.mobile,
                'tel': address.tel,
                'email': address.email
            }
            address_list.append(address_dict)
        context = {
            'addresses': address_list,
            'default_address_id': user.default_address_id
        }

        return render(request, 'user_center_site.html', context)


class CreateAddressView(LoginRequired):
    """添加收获地址"""

    def post(self, request):
        # 因为最多只有二十个收货地址，要先判断一下
        user = request.user  # 获取登陆用户
        count = Address.objects.filter(user=user, is_deleted=False).count()  # 收货地址的总数
        if count >= 20:
            return http.JsonResponse({'code': RETCODE.MAXNUM, 'errmsg': '超出库存上限'})
        # 接收参数
        json_dict = json.loads(request.body.decode())
        title = json_dict.get('title')
        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')
        # 校验参数
        if all([title, receiver, province_id, city_id, district_id, place, mobile]) is False:
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')
        # 处理业务逻辑
        try:
            # 保存数据到数据库
            address = Address.objects.create(
                user=user,
                title=title,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )
        except DatabaseError as e:
            logger.error(e)
            return http.HttpResponseForbidden('添加收货地址失败')
        # 添加默认地址
        if user.default_address is None:
            user.default_address = address
            user.save()
        # 构建返回信息给前端
        context = {
            'id': address.id,
            'title': address.title,
            'receiver': address.receiver,
            'province_id': address.province_id,
            'province': address.province.name,
            'city_id': address.city_id,
            'city': address.city.name,
            'district_id': address.district_id,
            'district': address.district.name,
            'place': address.place,
            'mobile': address.mobile,
            'tel': address.tel,
            'email': address.email
        }
        # 返回响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '添加收货地址成功', 'address': context})


class UpdateDetroyAddressView(LoginRequired):
    """修改和删除地址"""

    def put(self, request, address_id):
        try:
            address = Address.objects.get(id=address_id, user=request.user, is_deleted=False)
        except Address.DoesNotExist:
            return http.HttpResponseForbidden('修改地址失败')
        # 接受数据
        json_dict = json.loads(request.body.decode())
        title = json_dict.get('title')
        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')
        # 校验参数
        if not all([title, receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')

        # 处理业务逻辑
        # 数据修改更新一下
        try:
            # Address.objects.filter(id=address_id).update(
            #     user=request.user,
            #     title=receiver,
            #     receiver=receiver,
            #     province_id=province_id,
            #     city_id=city_id,
            #     district_id=district_id,
            #     place=place,
            #     mobile=mobile,
            #     tel=tel,
            #     email=email,
            # )
            address.title = title
            address.receiver = receiver
            address.province_id = province_id
            address.city_id = city_id
            address.district_id = district_id
            address.place = place
            address.mobile = mobile
            address.tel = tel
            address.email = email
            address.save()
        except Exception as e:
            logger.error('e')
            return http.HttpResponseForbidden('更新地址失败')
        # 构建响应数据，然后返回

        address_dict = {
            'id': address.id,
            'title': address.title,
            'receiver': address.receiver,
            'province_id': address.province_id,
            'province': address.province.name,
            'city_id': address.city_id,
            'city': address.city.name,
            'district_id': address.district_id,
            'district': address.district.name,
            'place': address.place,
            'mobile': address.mobile,
            'tel': address.tel,
            'email': address.email
        }

        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '更新地址成功', 'address': address_dict})

    def delete(self, request, address_id):

        try:
            address = Address.objects.get(id=address_id, user=request.user, is_deleted=False)
        except Address.DoesNotExist:
            return http.HttpResponseForbidden('删除地址失败')
        address.is_deleted = True
        address.save()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除地址成功'})


class DefaultAddressView(LoginRequired):
    """设置默认地址"""

    def put(self, request, address_id):
        try:
            address = Address.objects.get(id=address_id, user=request.user, is_deleted=False)
        except Address.DoesNotExist:
            return http.HttpResponseForbidden('设置默认地址失败')
        # 设置地址标题
        request.user.default_address = address
        request.user.save()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '设置默认地址成功'})


class UpdateTitleAddressView(LoginRequired):
    """修改地址标题"""

    def put(self, request, address_id):
        try:
            address = Address.objects.get(id=address_id, user=request.user, is_deleted=False)
        except Address.DoesNotExist:
            return http.HttpResponseForbidden('修改地址标题失败')
        json_dict = json.loads(request.body.decode())
        title = json_dict.get('title')
        if title is None:
            return http.HttpResponseForbidden('缺少必传参数')
        address.title = title
        address.save()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'Ok'})


class ChangepasswordView(LoginRequired):
    """修改密码"""

    def get(self, request):
        return render(request, 'user_center_pass.html')

    def post(self, request):
        # 接受修改的参数
        query_dict = request.POST
        old_pwd = query_dict.get('old_pwd')
        new_pwd = query_dict.get('new_pwd')
        new_cpwd = query_dict.get('new_cpwd')
        # 校验
        if all([old_pwd, new_cpwd, new_pwd]) is False:
            return http.HttpResponseForbidden('却少必传参数')
        # 判断旧密码是否正确
        user = request.user
        if user.check_password(old_pwd) is False:
            return render(request, 'user_center_pass.html', {'origin_pwd_errmsg': '旧密码输入不正确'})
        # if re.match(r'^[0-9A-Za-z]{8,20}$', new_pwd):
        #     return http.HttpResponseForbidden('请输入8-20位长度的密码')
        if not re.match(r'^[0-9A-Za-z]{8,20}$', new_pwd):
            return http.HttpResponseForbidden('请输入8-20位长度的密码')
        if new_cpwd != new_pwd:
            return http.HttpResponseForbidden('两次输入的密码不正确')

        # 修改密码
        user.set_password(new_pwd)
        user.save()

        #  重定向到登陆页面

        return redirect('/logout/')


class HistoryGoodsView(View):
    """商品浏览记录"""
    def post(self, request):
        # 保存商品浏览记录
        # 判断是否是登陆用户
        if not request.user.is_authenticated:
            return http.JsonResponse({'code': RETCODE.SESSIONERR, 'errmsg': '用户未登陆'})
        # 获取数据
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')
        # 校验
        try:
            sku = SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return http.HttpResponseForbidden('sku_id无效')
        # 创建redis连接对象
        redis_conn = get_redis_connection('history')
        # 创建管道
        pl = redis_conn.pipeline()
        # 存储每个用户redis的唯一key
        key = 'history_%s' % request.user.id
        # 先去重
        pl.lrem(key, 0, sku_id)
        '''
            Redis Lrem 根据参数 COUNT 的值，移除列表中与参数 VALUE 相等的元素。
            COUNT 的值可以是以下几种：
            count > 0 : 从表头开始向表尾搜索，移除与 VALUE 相等的元素，数量为 COUNT 。
            count < 0 : 从表尾开始向表头搜索，移除与 VALUE 相等的元素，数量为 COUNT 的绝对值。
            count = 0 : 移除表中所有与 VALUE 相等的值。
        '''
        # 添加到列表
        pl.lpush(key, sku_id)
        '''
        Redis Lpush 命令将一个或多个值插入到列表头部。 如果 key 不存在，一个空列表会被创建并执行 LPUSH 操作。 当 key 存在但不是列表        类型时，返回一个错误。
        注意：在Redis 2.4版本以前的 LPUSH 命令，都只接受单个 value 值。
        '''
        # 保留列表的前五个元素
        pl.ltrim(key, 0, 4)
        '''
        Redis Ltrim
        对一个列表进行修剪(trim)，就是说，让列表只保留指定区间内的元素，不在指定区间之内的元素都将被删除。
        下标0表示列表的第一个元素，
        1表示列表的第二个元素，以此类推。 
        你也可以使用负数下标，
        -1表示列表的最后一个元素， 
        -2表示列表的倒数第二个元素，以此类推。
        '''
        # 执行管道
        pl.execute()

        # 响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})

    def get(self, request):
        # 判断用户是否登陆
        if not request.user.is_authenticated:
            return http.JsonResponse({'code': RETCODE.SESSIONERR, 'errmsg': '用户未登陆'})
        # 连接对象
        redis_conn = get_redis_connection('history')
        # 存储每个用户redis的唯一key
        key = 'history_%s' % request.user.id
        # 获取当前用户的浏览记录数据
        sku_id_list = redis_conn.lrange(key, 0, -1)
        # 定义一个列表来装sku的字典数据
        sku_list = []
        # 遍历sku_id列表，有顺序的一个个的获取sku模型并转换成字典
        for sku_id in sku_id_list:
            sku = SKU.objects.get(id=sku_id)
            sku_list.append({
                'id': sku.id,
                'name': sku.name,
                'price': sku.price,
                'default_image_url': sku.default_image.url
            })
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'skus': sku_list})


class ForgetbeginView(View):
    def get(self, request):
        return render(request, 'find_password.html')

class ForgetoneView(View):
    def get(self, request, username_or_mobile):

        # 获取数据
        uuid = request.GET.get('image_code_id')
        image_code_client = request.GET.get('text')
        # 校验数据
        if all([username_or_mobile, uuid, image_code_client]) is False:
            return http.HttpResponseForbidden('缺少必传参数')
        user = get_user_by_account(username_or_mobile)
            # 创建连接到redis的对象
        redis_conn = get_redis_connection('verify_code')
            # 提取图形验证码
        image_code_server = redis_conn.get(uuid)
        if image_code_server is None:
                # 图形验证码过期或者不存在
            return http.JsonResponse({"error": "验证码错误"}, status=400)
            # 删除图形验证码，避免恶意测试图形验证码
        redis_conn.delete(uuid)
            # 对比图形验证码
        image_code_server = image_code_server.decode()  # bytes转字符串
        if image_code_client.lower() != image_code_server.lower():  # 转小写后比较
            return http.JsonResponse({"error": "验证码错误"}, status=400)
        access_token = {
            "user_id": user.id,
            "username": user.username,
            "mobile": user.mobile
        }
        # 转成字符串
        access_token_str= base64.b64encode(pickle.dumps(access_token)).decode()
        mobile = user.mobile
        mobile = mobile[:3] + '******' + mobile[9:]
        return http.JsonResponse({"error": "OK", 'mobile': mobile, 'access_token': access_token_str})


class ForgetTwoView(View):
    def get(self, request):
        access_token_str = request.GET.get('access_token')
        access_token = pickle.loads(base64.b64decode(access_token_str.encode()))
        user_id = access_token['user_id']
        username = access_token['username']
        mobile = access_token['mobile']
        if access_token_str is None:
            return http.HttpResponseForbidden('缺少必传参数')
        try:
            user = User.objects.get(id=user_id, username=username, mobile=mobile)
        except User.DoesNotExist:
            return http.JsonResponse({"error": "数据错误"}, status=400)
        sms_code = "%06d" % randint(100000, 999999)
        logger.info("SMS_CODE:%s" % sms_code)
        verify_code = get_redis_connection("verify_code")
        p1 = verify_code.pipeline()
        p1.setex("sms_code_%s" % mobile, 60, sms_code)
        p1.setex(mobile, 60, "1")
        p1.execute()
        send_sms_code.delay(mobile, sms_code)
        return http.JsonResponse({'message': 'ok'})

class ForgetThreeView(View):
    def get(self, request, username_or_mobile):
        # 通过url查询参数获取pickle加密的sms_code
        sms_code = request.GET.get("sms_code")

        if sms_code is None:
            return http.HttpResponseForbidden("缺少必传参数")

        try:
            user = User.objects.get(username=username_or_mobile, is_active=True) or User.objects.get(mobile=username_or_mobile, is_active=True)
        except User.DoesNotExist:
            return http.HttpResponseForbidden("缺少必传参数")
        mobile = user.mobile
        sms_redis = get_redis_connection("verify_code")
        sms_code_server = sms_redis.get("sms_code_%s" % mobile)
        sms_redis.delete("sms_code_%s" % mobile)
        if sms_code_server is None:
            return http.JsonResponse({"error": "验证码错误"}, status=400)
        if sms_code != sms_code_server.decode():
            return http.JsonResponse({"error": "手机号不存在"}, status=404)
        access_token = {
            "user_id": user.id,
            "mobile": user.mobile,
            "username": user.username,
        }
        access_token_str = base64.b64encode(pickle.dumps(access_token)).decode()
        return http.JsonResponse({"message": "ok", "user_id": user.id, "access_token": access_token_str})

class ForgetFourView(View):
    def post(self, request, user_id):
        json_dict = json.loads(request.body.decode())
        access_token_str = json_dict.get('access_token')
        password = json_dict.get('password')
        password2 = json_dict.get('password2')
        if not all([access_token_str, password, password2]):
            return http.HttpResponseForbidden("缺少必传参数")
        if not re.match(r'[0-9A-Za-z]{8,20}', password):
            return http.JsonResponse({"message": "密码最少8位，最长20位"})
        if password != password2:
            return http.JsonResponse({"message": "两次输入的密码不一致"})
        access_token = pickle.loads(base64.b64decode(access_token_str.encode()))
        username = access_token['username']
        try:
            user = User.objects.get(username=username, is_active=True)
        except User.DoesNotExist:
            return http.JsonResponse({'error': '数据错误'}, status=400)
        user.set_password(password)
        user.save()
        return http.JsonResponse({"message": "ok"})











